﻿using Aliyun.OSS;
using WAU.Models.Framework;
using WAU.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Net;
using System.Text;
using static WAU.Utils.AliOSSUtils;
using WAU.Models;

namespace WAU.Controllers.UniverseModule
{
    /// <summary>
    /// 通用接口的控制器
    /// </summary>
    [ApiController]
    [Route("/api/[controller]/[action]")]
    public class UniverseController : ControllerBase
    {
        /// <summary>
        /// ctor
        /// </summary>
        public UniverseController() { }

        /// <summary>
        /// 获取新的 GUID 值
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public ActionResult GetNEWGUID()
        {
            return Ok(new Result
            {
                Success = true,
                Data = GuidFactory.NewSortedGuid()
            });
        }

        /// <summary>
        /// 获取短的 Guid（8位）
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public ActionResult GetShortUniqueId()
        {
            return Ok(new Result
            {
                Success = true,
                Data = CommonUtils.GenerateShortUniqueId(8)
            });
        }

        #region 上传文件

        // TODO 直接匹配给定的目录是否更简单些
        /// <summary>
        /// 目录可能带有的敏感字符（可以通过这个上传到设定外的目录）
        /// </summary>
        private static string[] SENSITIVE_IN_DIR_STRINGS = new string[] { "../", "..\\" };

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="_"></param>
        /// <param name="dir"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult UploadFiles([FromForm] IFormFile _, [FromForm] string dir)
        {
            bool isDirStrSensitive = SENSITIVE_IN_DIR_STRINGS.Any(a => dir.Contains(a));
            if (isDirStrSensitive)
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "路径中有敏感字符，上传文件失败"
                });
            }

            List<IFormFile> formFiles = Request.Form.Files.Select(a => a).ToList();
            if (formFiles.Count == 0)
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "没有要上传的文件"
                });
            }
            var result = CommonUtils.SaveUploadFiles(formFiles, dir);
            if (!result.Success)
            {
                return Ok(new Result
                {
                    Success = false,
                    Msg = "上传文件失败"
                });
            }

            string prefix = Request.Scheme + "://" + Request.Host;
            List<string> pathes = ((List<string>)result.Data).Select(a => $"{ prefix }/{ CommonUtils.ConvertLocalFSPathToURLPart(a) }").ToList();
            return Ok(new Result
            {
                Success = true,
                Msg = "上传文件成功",
                Data = pathes
            });
        }

        /// <summary>
        /// 上传较大文件
        /// 参考代码：https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/mvc/models/file-uploads/samples/5.x/LargeFilesSample/Controllers/FileUploadController.cs
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<IActionResult> UploadLargeFiles() // TODO 根据实际改造此方法
        {
            var request = HttpContext.Request;

            // validation of Content-Type
            // 1. first, it must be a form-data request
            // 2. a boundary should be found in the Content-Type
            if (!request.HasFormContentType ||
                !MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
                string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
            {
                return new UnsupportedMediaTypeResult();
            }

            // 是否确实有文件上传
            bool hasUploadedFile = false;

            var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
            var section = await reader.ReadNextSectionAsync();

            // This sample try to get the first file from request and save it
            // Make changes according to your needs in actual use
            while (section != null)
            {
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
                    out var contentDisposition);

                if (hasContentDispositionHeader && contentDisposition.DispositionType.Equals("form-data") &&
                    !string.IsNullOrEmpty(contentDisposition.FileName.Value))
                {
                    // Don't trust any file name, file extension, and file data from the request unless you trust them completely
                    // Otherwise, it is very likely to cause problems such as virus uploading, disk filling, etc
                    // In short, it is necessary to restrict and verify the upload
                    // Here, we just use the temporary folder and a random file name

                    // Get the temporary folder, and combine a random file name with it
                    var fileName = Path.GetRandomFileName();
                    var saveToPath = Path.Combine(Path.GetTempPath(), fileName);

                    using (var targetStream = System.IO.File.Create(saveToPath))
                    {
                        await section.Body.CopyToAsync(targetStream);
                    }

                    hasUploadedFile = true;
                }

                section = await reader.ReadNextSectionAsync();
            }

            if (hasUploadedFile)
            {
                return Ok();
            }
            else
            {
                // If the code runs to this location, it means that no files have been saved
                return BadRequest("No files data in the request.");
            }            
        }

        #endregion

        #region 阿里云 OSS

        /// <summary>
        /// 获取 OSS 的上传 Policy（密钥串等）
        /// </summary>
        /// <returns></returns>
        // TODO 应改为授权
        // TODO 如果需要传入回调等，则需要 转移进 Service 中
        [HttpPost]
        [Authorize]
        public ActionResult GetOSSPostPolicy()
        {
            string userId = HttpContext.User.Identity.Name;

            PolicyConditions policyConditions = new PolicyConditions();
            policyConditions.AddConditionItem(PolicyConditions.CondContentLengthRange, 1, 200 * 1024 * 1024);   // 设置上传上限为 200M

            string postPolicy = GeneratePostPolicy(DateTime.Now.AddHours(1), policyConditions);  // 过期时间 1 小时
            string postPolicyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(postPolicy));

            var callbackParam = new CallbackParam()
            {
                //CallbackUrl = $"{ Request.Scheme }://{ Request.Host }/api/{ nameof(UniverseController).Replace(nameof(Controller), "") }/{ nameof(OSSPostCallback) }",
                CallbackUrl = GlobalConfig.Setting.OSS.CallbackUrl,
                CallbackBody = $"userId={ userId }&{ CallbackParam.CALLBACKBODY_ORIGIN }"   // 将用户的 id 放入回调中
            };
            string callbackParamBase64 = Convert.ToBase64String(
                                            Encoding.UTF8.GetBytes(
                                                JsonConvert.SerializeObject(callbackParam, new JsonSerializerSettings
                                                {
                                                    ContractResolver = new CamelCasePropertyNamesContractResolver() // 将属性名全部小写驼峰
                                                })));

            string url = GlobalConfig.Setting.OSS.EndPoint.Replace("https://", $"https://{GlobalConfig.Setting.OSS.BucketName }.");
            AliOSSPostPolicy ossPolicy = new AliOSSPostPolicy
            {
                URL = url,
                OSSAccessKeyId = GlobalConfig.Setting.OSS.AccessKeyId,
                Policy = postPolicyBase64,
                Signature = ComputeOSSSignature(GlobalConfig.Setting.OSS.AccessKeySecret, postPolicyBase64),
                //Callback = callbackParamBase64    // TODO 恢复回调
            };

            return Ok(new Result
            {
                Success = true,
                Data = ossPolicy
            });
        }

        /// <summary>
        /// OSS Post 的回调 Dto
        /// </summary>
        public class OSSPostCallbackDto
        {
            /// <summary>
            /// Bucket 名称
            /// </summary>
            public string Bucket { get; set; }
            /// <summary>
            /// Object 名称
            /// </summary>
            public string Object { get; set; }
            /// <summary>
            /// 进行 OSS post 的 userId
            /// </summary>
            public string UserId { get; set; }
            /// <summary>
            /// 对象名（文件名）
            /// </summary>
            public string FileName { get; set; }
            /// <summary>
            /// 文件大小
            /// </summary>
            public long Size { get; set; }
            /// <summary>
            /// MIME 类型
            /// </summary>
            public string MimeType { get; set; }
        }

        /// <summary>
        /// OSS Post 的回调
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost]
        public Result OSSPostCallback([FromForm]OSSPostCallbackDto dto)
        {
            // TODO 可在此处记录用户上传
            bool isCallbackValid = AliOSSUtils.VerifyCallBackSignature(HttpContext.Request);
            if (isCallbackValid)
            {
                Console.WriteLine("OSS回调签名验证成功");
                return new Result { Success = true };
            }
            else
            {
                Console.WriteLine(isCallbackValid);
                
                Console.WriteLine(string.Join("，", Request.Headers.Keys.Select(a => $"key: { a }, value: { Request.Headers[a] }\n")));
                Console.WriteLine(dto.UserId);
                Console.WriteLine(dto.FileName);
                Console.WriteLine(dto.Size);
                Console.WriteLine(dto.MimeType);
                return null;
            }
        }

        /// <summary>
        /// 获取 OSS 的授权后地址
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult GetOSSUrlWithSign(string url)
        {
            return Ok(new Result
            {
                Success = true,
                Data = AliOSSUtils.GetOSSUrlWithSign(url)
            });
        }

        #endregion
    }
}
